﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RedLockNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace Handday.Erp.Core
{
    /// <summary>
    /// 后台任务实现
    /// </summary>
    public class BackgroundJobService : IBackgroundJobService
    {
        private readonly Channel<Tuple<string, LambdaExpression>> _channel;
        private readonly ILogger<BackgroundJobService> _logger;
        private readonly IServiceProvider _serviceProvider;
        private readonly IOptions<BackgroundJobOption> _options;

        /// <summary>
        /// ctor
        /// </summary>
        public BackgroundJobService(
            ILogger<BackgroundJobService> logger,
            IServiceProvider serviceProvider,
            IOptions<BackgroundJobOption> options)
        {
            _channel = Channel.CreateUnbounded<Tuple<string, LambdaExpression>>();
            _logger = logger;
            _serviceProvider = serviceProvider;
            _options = options;
        }

        /// <summary>
        /// hostservice 调用
        /// </summary>
        public Task Execute(CancellationToken cancellationToken)
        {
            for (var i = 0; i < _options.Value.ThreadCount; i++)
            {
                Task.Run(async () =>
                {
                    while (!cancellationToken.IsCancellationRequested)
                    {
                        var message = string.Empty;
                        try
                        {
                            var tuple = await _channel.Reader.ReadAsync(cancellationToken);
                            var lamda = tuple?.Item2;
                            if (tuple != null && lamda != null)
                            {
                                using (var scope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
                                {
                                    var lockFactory = scope.ServiceProvider.GetService<IDistributedLockFactory>();
                                    if (!string.IsNullOrWhiteSpace(tuple.Item1) && lockFactory != null)
                                    {
                                        await using (var redisLock = await lockFactory.CreateLockAsync($"erp_{tuple.Item1}", TimeSpan.FromSeconds(20)))
                                        {
                                            message = redisLock.IsAcquired ? await Do(scope, lamda) : $"{tuple.Item1},执行中";
                                        }
                                    }
                                    else
                                    {
                                        message = await Do(scope, lamda);
                                    }
                                }

                                _logger.LogInformation($"后台任务统一执行成功,队列数量:{(_channel?.Reader?.CanCount ?? false ? (_channel?.Reader?.Count) : 0)},{message}");
                            }
                        }
                        catch (Exception e)
                        {
                            _logger.LogError($"后台任务统一执行错误,队列数量:{(_channel?.Reader?.CanCount ?? false ? (_channel?.Reader?.Count) : 0)},{message},{e.Message}_{e.StackTrace},{e.TargetSite}");
                        }
                    }
                }, cancellationToken);
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// add background job
        /// </summary>
        /// <typeparam name="IService">注入的服务</typeparam>
        /// <param name="expression">lamada表达式</param>
        /// <param name="lockKey">分布式锁key;空,不执行并发锁定;请根据实际情况传值</param>
        public void Transfer<IService>(Expression<Func<IService, Task>> expression, string lockKey = null)
        {
            AddLamda(expression, lockKey);
        }

        /// <summary>
        /// add background job
        /// </summary>
        /// <param name="expression">lamada表达式</param>
        /// <param name="lockKey">分布式锁key;空,不执行并发锁定;请根据实际情况传值</param>
        public void Transfer(Expression<Action> expression, string lockKey = null)
        {
            AddLamda(expression, lockKey);
        }

        #region private methods

        private void AddLamda(LambdaExpression expression, string lockKey)
        {
            try
            {
                if (expression != null)
                {
                    _channel.Writer.TryWrite(Tuple.Create(lockKey, expression));
                }
            }
            catch { }
        }

        private async Task<string> Do(IServiceScope scope, LambdaExpression lamda)
        {
            var message = string.Empty;
            var action = lamda.Compile();
            var parameters = lamda.Parameters;
            var pars = new List<object>();
            if (parameters.Any())
            {
                var type = parameters[0].Type;
                message = $"{type.FullName},{lamda.Body}";
                var param = scope.ServiceProvider.GetRequiredService(type);
                pars.Add(param);
            }

            if (action.Method.ReturnType == typeof(Task))
            {
                await (Task)action.DynamicInvoke(pars.ToArray());
            }
            else
            {
                action.DynamicInvoke(pars.ToArray());
            }

            return await Task.FromResult(message);
        }

        #endregion
    }
}
